// The main purpos of this snippet is to show that the constexpr identifier can
// reduce the size of executable file, which means that it can be used as an
// alternative to conventional Macros, while the most significant advantages are
// compile time type-safety check and syntax check.
#include <iostream>

// LogLevel enums
enum class LogLevel
{
    kTrace,
    kDebug,
    kInfo,
    kWarn,
    kError,
    kFatal
};

#ifndef NDEBUG
constexpr LogLevel logLevel = LogLevel::kTrace;
#else
constexpr LogLevel logLevel = LogLevel::kInfo;
#endif

#define RED "\033[1;31m"
#define WHITE "\033[1;37m"
#define RESET "\033[0m"
#define CONSOLE_ std::cout << __FILE__ << "[" << __LINE__ << "]: "

#define LOGDEF(dbg_, lvl_, str_, color_)    \
    template <class Callable>               \
    constexpr void dbg_(const Callable& c)  \
    {                                       \
        if constexpr (lvl_ >= logLevel)     \
        {                                   \
            std::cout << color_ str_ RESET; \
            c();                            \
        }                                   \
    }

LOGDEF(Debug, LogLevel::kDebug, "Debug: ", WHITE)

LOGDEF(Warn, LogLevel::kWarn, "Warn: ", RED)

void foo()
{
    CONSOLE_ << "message\n";
}

using namespace std;
int main(int argc, char** argv)
{
    Debug(foo);
    Warn(foo);
    return 0;
}